package br.srv.full.virtual.engine.users;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.ejb.EJB;
import javax.ejb.Stateful;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import br.srv.full.persistence.BeansManagerException;
import br.srv.full.persistence.PersistBeansException;
import br.srv.full.persistence.RefreshBeansException;
import br.srv.full.persistence.UpdateBeansException;
import br.srv.full.persistence.utils.BeansManagerSupport;
import br.srv.full.persistence.utils.PersistenceUtils;
import br.srv.full.virtual.bean.base.QueryID;
import br.srv.full.virtual.engine.message.MessageManagerLocal;
import br.srv.full.virtual.engine.number.NumberGeneratorLocal; 
import br.srv.full.virtual.entities.auth.Login;
import br.srv.full.virtual.entities.auth.Person;
import br.srv.full.virtual.entities.auth.Role;
import br.srv.full.virtual.entities.auth.User;
import br.srv.full.virtual.entities.log.SiteUseControl;
import br.srv.full.virtual.entities.resume.UserLogin;

@Stateful(name = "PersonsManager", description = "Gerencia Entidades que representam pessoas no sistema!")
@TransactionManagement(TransactionManagementType.CONTAINER)
public class PersonsManagerEJB3 implements PersonsManagerLocal, PersonsManagerRemote {

	/**
	 * 
	 */
	private static final long serialVersionUID = 6122134726009373720L;

	public PersonsManagerEJB3() {
		log.info("criado!");
	}

	private final Log log = LogFactory.getLog(getClass());

	@PersistenceContext
	private EntityManager em;

	private BeansManagerSupport beansManager = new BeansManagerSupport();

	@EJB
	private MessageManagerLocal messageManager;

	@EJB
	private NumberGeneratorLocal numberGenerator;

	// @Resource(mappedName = "sendMessageConfirmation")
	private Boolean sendMessageConfirmation;

	{
		try {
			InitialContext ictx = new InitialContext();
			Context env;
			Object obj;
			try {
				String l_string = "java:comp/env";
				env = (Context) ictx.lookup(l_string);
				obj = env.lookup("sendMessageConfirmation");
			} catch (Exception e) {
				log.warn(e);
				obj = ictx.lookup("sendMessageCofirmation");
			}
			sendMessageConfirmation = (Boolean) obj;
		} catch (Exception e) {
			log.warn(e);
			sendMessageConfirmation = false;
		}
	}

	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public UserLogin confirmRegistration(UserLogin p_userLogin) {
		UUID l_uuid = numberGenerator.generateUUID();
		p_userLogin.setRegistrationId(l_uuid.toString());
		if (p_userLogin.getUserLogin() == null) {
			p_userLogin.setUserLogin(p_userLogin.getEmail());
		}

		UserLogin l_userLogin = saveUserRegistration(p_userLogin);

		if (sendMessageConfirmation)
			try {
				messageManager.sendMessageNewUserRegistration(p_userLogin);
			} catch (PersonsManagerException e) {
				e.printStackTrace();
				log.warn("Falha ao enviar mensagem de confirmação", e);
			}
		// } catch (SendMessaginLoginManagerException _e) {
		// lastErrorMessage = _e.getMessage();
		// log.warn(_e);
		// return ActionLoginManager.failSendMessage;
		// } catch (LoginManagerExeception e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		return l_userLogin;
	}

	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	private UserLogin saveUserRegistration(UserLogin p_userLogin) {
		log.info("Persistindo user Login");
		UserLogin l_userLogin = p_userLogin;

		try {
			l_userLogin.setRegistrationDate(new Date());

			User l_user = beansManager.persist(em, l_userLogin.getUser());
			Login l_login = l_userLogin.getLogin();
			l_login.setUser(l_user);
			l_userLogin.setUser(l_user);

			l_login = beansManager.persist(em, l_login);
			l_userLogin.setLogin(l_login);
		} catch (PersistBeansException e) {
			log.warn(e);
		} catch (BeansManagerException e) {
			log.warn(e);
		}
		log.info("User Login persistido");
		return l_userLogin;
	}

	@TransactionAttribute(TransactionAttributeType.SUPPORTS)
	public boolean consultIfEmailExist(UserLogin p_userLogin) {

		boolean l_userExist = false;
		log.info("Pesquisando se e-mail já é cadastrado: " + p_userLogin.getEmail());
		l_userExist |= beansManager.find(em, User.class, User.FindByEMail, p_userLogin.getEmail()) != null;
		l_userExist |= beansManager.find(em, Login.class, Login.FIND_BY_LOGIN, p_userLogin.getEmail()) != null;

		return l_userExist;
	}

	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public UserLogin update(UserLogin p_userLogin) throws PersonsManagerException {
		if (p_userLogin == null)
			throw new UserLoginException("UserLogin náo pode ser NULL!");
		else if (p_userLogin.getUserId() != null && p_userLogin.getLoginId() != null
				&& p_userLogin.getRegistrationId() != null && p_userLogin.getRegistrationId().trim().isEmpty())
			throw new UserLoginException("UserLogin deve fazer referencia a um registro existente no banco!");
		try {
			Login l_login = beansManager.update(em, p_userLogin.getLogin());
			User l_user = beansManager.update(em, p_userLogin.getUser());
			p_userLogin.setLogin(l_login);
			p_userLogin.setUser(l_user);

		} catch (UpdateBeansException e) {
			log.warn("Problema ao fazer update do Usuario e seu atual Login", e);
			e.printStackTrace();
			throw new PersonsManagerException(e);
		}
		return p_userLogin;
	}

	/**
	 * Pesquisa no cadastro a existencia do usuario conforme a querie informada.
	 * 
	 * @param l_querieString
	 * @param p_key
	 * @return
	 * 
	 * @deprecated Evite usar conceitos da camada de persistencia acima dos
	 *             gerenciadores de entidade. (QueryID)
	 * @see #findUserLoginByUUID(String)
	 */
	@Deprecated
	@TransactionAttribute(TransactionAttributeType.SUPPORTS)
	public List<UserLogin> findUserLogin(QueryID l_querieString, String p_key) {

		List<UserLogin> l_userLogins = null;
		if (l_querieString.getOwner().equals(User.class)) {
			User l_user = (User) beansManager.find(em, User.class, l_querieString, p_key);
			if (l_user != null) {
				List<Login> l_logins = l_user.getLogins();
				if (l_logins != null && l_logins.size() > 0) {
					l_userLogins = new ArrayList<UserLogin>(l_logins.size());
					for (Login l_loginTmp : l_logins) {
						l_userLogins.add(new UserLogin(l_user, l_loginTmp));
					}
				}
			}
		} else if (l_querieString.getOwner().equals(Login.class)) {
			Login l_login = (Login) beansManager.find(em, Login.class, l_querieString, p_key);
			if (l_login != null) {
				l_userLogins = new ArrayList<UserLogin>(1);
				l_userLogins.add(new UserLogin(l_login));
			}
		} else {
			throw new RuntimeException("Deve ser utilizado Uma query baseada em User ou Login!");
		}
		return l_userLogins;
	}

	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public List<UserLogin> findUserLoginByUUID(String p_uuid) throws BeansManagerException {

		List<UserLogin> l_userLogins = null;
		User l_user = (User) beansManager.find(em, User.class, User.FindByUUID, p_uuid);
		if (l_user != null) {
			try {
				l_user = beansManager.refresh(em, l_user, User.FIELD_LOGINS);
			} catch (RefreshBeansException e) {
				log.warn("Problemas ao atualizar o campo 'Logins'", e);
				throw new BeansManagerException("Problemas ao atualizar o campo 'Logins'", e);
			}

			List<Login> l_logins = l_user.getLogins();
			if (l_logins != null && l_logins.size() > 0) {
				l_userLogins = new ArrayList<UserLogin>(l_logins.size());
				for (Login l_loginTmp : l_logins) {
					l_userLogins.add(new UserLogin(l_user, l_loginTmp));
				}
			}
		}

		return l_userLogins;
	}

	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public synchronized UserLogin changePassword(UserLogin p_userLogin, String p_newPasswd)
			throws PersonsManagerException {
		if (p_newPasswd == null)
			throw new PasswordErrorException("Senha náo pode ser NULL!");
		else if (p_newPasswd.trim().isEmpty())
			throw new PasswordErrorException("Senha não pode ser vazia ou espaço em branco!");
		else if (p_userLogin == null)
			throw new UserLoginException("UserLogin náo pode ser NULL!");
		else if (p_userLogin.getUserId() != null && p_userLogin.getLoginId() != null
				&& p_userLogin.getRegistrationId() != null && p_userLogin.getRegistrationId().trim().isEmpty())
			throw new UserLoginException("UserLogin deve fazer referencia a um registro existente no banco!");
		else if (p_newPasswd.equals(p_userLogin.getPassword()))
			throw new PasswordErrorException("Senha não foi trocada porque são iguais!");

		Login l_loginDB;

		log.info("Consultando no Banco se existe o Login: " + p_userLogin + " no registro de ID: "
				+ p_userLogin.getRegistrationId());
		l_loginDB = (Login) beansManager.find(em, Login.class, p_userLogin.getLoginId());

		if (l_loginDB == null)
			throw new PersonsManagerException("O Usário informado náo tem Login identificado no Banco!");
		// else if (!l_userDB.getLoginId().equals(p_userLogin.getLoginId())
		// && l_userDB.getUserId().equals(p_userLogin.getUserId()))
		// throw new PersonsManagerException(
		// "O UserLogin infomrado náo confere com as informações amarzenadas no
		// banco!");

		log.info("Trocando a Senha de " + l_loginDB);
		l_loginDB.setPassword(p_newPasswd);
		try {
			beansManager.update(em, l_loginDB);
		} catch (UpdateBeansException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		p_userLogin.setLogin(l_loginDB);

		return p_userLogin;
	}

	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public synchronized UserLogin increaseAccessCount(UserLogin p_userLogin) throws PersonsManagerException {
		if (p_userLogin == null)
			throw new PersonsManagerException("UserLogin náo pode ser NULL!");
		else if (p_userLogin.getUserId() != null && p_userLogin.getLoginId() != null
				&& p_userLogin.getRegistrationId() != null && p_userLogin.getRegistrationId().trim().isEmpty())
			throw new PersonsManagerException("UserLogin deve fazer referencia a um registro existente no banco!");

		Integer l_atualCount = p_userLogin.getAccessCount();
		int l_newCount = l_atualCount == null ? 1 : l_atualCount + 1;
		p_userLogin.setAccessCount(l_newCount);
		try {
			beansManager.update(em, p_userLogin.getLogin());
		} catch (UpdateBeansException e) {
			e.printStackTrace();
			log.warn("Problema ao incrementar a contagem de acessos do Login", e);
			p_userLogin.setAccessCount(l_atualCount);
			throw new PersonsManagerException(e);
		}
		return p_userLogin;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.srv.full.virtual.engine.users.PersonsManager#logSiteUse(br.srv.full.virtual.entities.resume.UserLogin)
	 */
	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public void logSiteUse(UserLogin p_userLogin, String p_msg) throws PersonsManagerException {
		Login l_login = getLogin(p_userLogin);
		logSiteUse(l_login, p_msg);
	}

	/**
	 * @param p_login
	 * @throws PersonsManagerException
	 */
	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public void logSiteUse(Login p_login, String p_msg) throws PersonsManagerException {
		try {
			Login l_login = beansManager.refresh(em, p_login);
			SiteUseControl l_suc = new SiteUseControl(l_login);
			l_suc.setMessage(p_msg);
			beansManager.persist(em, l_suc);
		} catch (PersistBeansException e) {
			log.warn("Problemas ao criar log para usuario: " + p_login, e);
			throw new PersonsManagerException(e);
		} catch (RefreshBeansException e) {
			log.warn("Problemas ao refresh do login: " + p_login, e);
			throw new PersonsManagerException(e);
		} catch (BeansManagerException e) {
			log.warn(e);
		}
	}

	/**
	 * @param p_userLogin
	 * @return
	 */
	public Login getLogin(UserLogin p_userLogin) {
		return beansManager.find(em, Login.class, p_userLogin.getLoginId());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.srv.full.virtual.engine.users.PersonsManager#createFirstLogin(br.srv.full.virtual.entities.resume.UserLogin)
	 */
	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public synchronized UserLogin createFirstLogin(UserLogin p_userLogin) throws FirstLoginException,
			PersonsManagerException {
		User l_user = beansManager.find(em, User.class, p_userLogin.getUserId());
		if (l_user == null)
			throw new PersonsManagerException("Usário não encontrado!");

		List<Login> l_logins = l_user.getLogins();
		if (l_logins != null && l_logins.size() > 0) {
			int l_size = l_logins.size();
			for (Login l_login : l_logins) {
				if (l_login != null && l_login.getPassword() != null)
					throw new FirstLoginException("Usuário já Possui " + l_size + "Login" + (l_size > 1 ? "" : "s")
							+ " Cadastrado!");
			}
		}
		Login l_newLogin;
		if (l_logins == null)
			l_newLogin = new Login(l_user);
		else
			l_newLogin = l_logins.get(0);

		try {

			if (l_newLogin.getId() == null)
				l_newLogin = beansManager.persist(em, l_newLogin);
			else
				l_newLogin = beansManager.update(em, l_newLogin);
			l_user.addLogin(l_newLogin);
			l_user = beansManager.update(em, l_user);

		} catch (PersistBeansException e) {
			log.warn("Falha ao persistir o Bean", e);
			throw new PersonsManagerException(e);
		} catch (UpdateBeansException e) {
			log.warn("Falha ao atualizar o Bean", e);
			throw new PersonsManagerException(e);
		} catch (BeansManagerException e) {
			log.warn(e);
		}
		p_userLogin.setLogin(l_newLogin);

		return p_userLogin;

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.srv.full.virtual.engine.users.PersonsManager#updateUserRoles(br.srv.full.virtual.entities.resume.UserLogin)
	 */
	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public UserLogin refreshUserLogin(UserLogin p_userLogin) {

		Login l_login;
		UserLogin l_userLogin = p_userLogin;
		try {

			l_login = p_userLogin.getLogin();
			if (l_login != null && l_login.getId() != null)
				l_login = beansManager.refresh(em, l_login);

			p_userLogin.setLogin(l_login);

		} catch (RefreshBeansException e) {
			log.warn("problemas ao executar o Refresh do Usuário " + p_userLogin, e);
			log.warn("Será mantido as informações!");
		}

		return l_userLogin;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.srv.full.virtual.engine.users.PersonsManager#persist(br.srv.full.virtual.entities.base.User)
	 */
	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public <bean extends Person> bean update(bean p_person) throws PersonsManagerException {
		if (p_person == null)
			throw new UserLoginException("UserLogin náo pode ser NULL!");
		else if (p_person.getId() != null && p_person.getRegistrationId() != null
				&& p_person.getRegistrationId().trim().isEmpty())
			throw new UserLoginException("User  deve fazer referencia a um registro existente no banco!");
		bean l_user;
		try {
			p_person.setLastUpdateDate(new Date());
			l_user = beansManager.update(em, p_person);

		} catch (UpdateBeansException e) {
			log.warn("Problema ao fazer update do Usuario e seu atual Login", e);
			throw new PersonsManagerException(e);
		}
		return l_user;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.srv.full.virtual.engine.users.PersonsManager#refresWorks(br.srv.full.virtual.entities.base.User)
	 */
	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public <bean extends Person> bean refresh(bean p_user, String... p_properties) throws PersonsManagerException {

		bean l_refresh = null;
		try {
			PersistenceUtils.validatePersistenceBean(p_user);

			l_refresh = beansManager.refresh(em, p_user, p_properties);

			l_refresh = copyBeanForTransport(l_refresh, p_properties);

		} catch (Exception e) {
			throw new PersonsManagerException(e);
		}
		return l_refresh;
	}

	/**
	 * @param <bean>
	 * @param p_bean
	 * @param p_properties
	 * @throws CopyBeanForTransportException
	 */
	@SuppressWarnings("unchecked")
	private <bean> bean copyBeanForTransport(bean p_bean, String... p_properties) throws CopyBeanForTransportException {
		bean l_transport = null;
		try {
			l_transport = (bean) p_bean.getClass().newInstance();

			BeanUtils.copyProperties(l_transport, p_bean);

			for (String l_property : p_properties) {
				try {
					Object l_obj = PropertyUtils.getProperty(p_bean, l_property);
					if (l_obj instanceof Collection) {
						Collection l_collection = (Collection) l_obj;
						l_obj = new ArrayList(l_collection);
					} else if (l_obj instanceof Map) {
						Map l_map = (Map) l_obj;
						l_obj = new HashMap(l_map);
					}
					PropertyUtils.setProperty(l_transport, l_property, l_obj);
				} catch (Exception e) {
					log.warn("Falha ao traduzir as propriedades!", e);
				}
			}
		} catch (Exception e) {
			throw new CopyBeanForTransportException(e);
		}

		return l_transport;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.srv.full.virtual.engine.users.PersonsManager#persist(java.lang.Object)
	 */
	@Override
	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public <bean extends Person> bean persist(bean p_person) throws PersonsManagerException {
		if (p_person == null)
			throw new UserLoginException("Pessoa náo pode ser NULL!");
		else if (p_person.getRegistrationId() == null || p_person.getRegistrationId().trim().isEmpty())
			throw new UserLoginException("Pessoa deve ter um UUID designado, " + "se for nova pessoa, "
					+ "use o metodo Personsmanager#persistNew(Pessoa)!");
		bean l_result;
		try {
			l_result = beansManager.persist(em, p_person);
		} catch (Exception e) {
			throw new PersonsManagerException(e);
		}
		return l_result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.srv.full.virtual.engine.users.PersonsManager#persist(br.srv.full.virtual.entities.base.Person,
	 *      java.lang.String[])
	 */
	@Override
	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public <bean extends Person> bean persist(bean p_person, String... p_properties) throws PersonsManagerException {
		if (p_person == null)
			throw new UserLoginException("UserLogin náo pode ser NULL!");
		else if (p_person.getId() != null && p_person.getRegistrationId() != null
				&& p_person.getRegistrationId().trim().isEmpty())
			throw new UserLoginException("User deve fazer referencia a um registro existente no banco!");
		bean l_result;
		try {
			l_result = beansManager.persist(em, p_person, p_properties);
		} catch (Exception e) {
			throw new PersonsManagerException(e);
		}
		return l_result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.srv.full.virtual.engine.users.PersonsManager#update(br.srv.full.virtual.entities.base.Person,
	 *      java.lang.String[])
	 */
	@Override
	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public <bean extends Person> bean update(bean p_person, String... p_properties) throws PersonsManagerException {
		if (p_person == null)
			throw new UserLoginException("UserLogin náo pode ser NULL!");
		else if (p_person.getId() != null && p_person.getRegistrationId() != null
				&& p_person.getRegistrationId().trim().isEmpty())
			throw new UserLoginException("User deve fazer referencia a um registro existente no banco!");
		bean l_user;
		try {
			p_person.setLastUpdateDate(new Date());
			l_user = beansManager.update(em, p_person, p_properties);

		} catch (UpdateBeansException e) {
			log.warn("Problema ao fazer update desta pessoa", e);
			throw new PersonsManagerException(e);
		} catch (BeansManagerException e) {
			log.warn("Problema ao fazer update pessoa", e);
			throw new PersonsManagerException(e);
		}
		return l_user;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.srv.full.virtual.engine.users.PersonsManager#persistNew(br.srv.full.virtual.entities.base.Person)
	 */
	@Override
	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	public <bean extends Person> bean persistNew(bean p_person) throws PersonsManagerException {
		if (p_person == null)
			throw new PersonsManagerException("UserLogin náo pode ser NULL!");
		else if (p_person.getRegistrationId() != null && !p_person.getRegistrationId().trim().isEmpty())
			throw new PersonsManagerException(
					"Somente pode persistir uma nova pessoa se ela náo tiver um Registro no Sistema!");
		else if (p_person.getId() != null)
			throw new PersonsManagerException(
					"Somente pode persistir uma nova pessoa se ela náo tiver um  Id do Banco!");

		bean l_user;
		try {
			{
				UUID l_uuid = numberGenerator.generateUUID();
				p_person.setRegistrationId(l_uuid.toString());
			}

			l_user = persist(p_person);
		} catch (Exception e) {
			log.warn("Problemas ao Persistir a nova pessoa: " + p_person, e);
			throw new PersonsManagerException(e);
		}
		return l_user;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.srv.full.virtual.engine.users.PersonsManager#find(java.lang.Class)
	 */
	@Override
	@TransactionAttribute(TransactionAttributeType.SUPPORTS)
	public <bean extends Person> Object find(Class<bean> p_beanClass) {
		return beansManager.find(em, p_beanClass);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.srv.full.virtual.engine.users.PersonsManager#getWorkerRoles()
	 */
	@SuppressWarnings("unchecked")
	@Override
	@TransactionAttribute(TransactionAttributeType.SUPPORTS)
	public List<Role> getWorkerRoles() {
		Object l_find = beansManager.find(em, Role.class, Role.FIND_ROLE_FOR_WORKER);
		List<Role> l_itens = null;
		if (l_find instanceof Collection) {
			Collection l_roles = (Collection) l_find;
			l_itens = new ArrayList<Role>(l_roles);
			for (Object l_object : l_roles) {
				Role l_workerRole = (Role) l_object;
				l_itens.add(l_workerRole);
			}

		} else if (l_find instanceof Role) {
			l_itens = new ArrayList<Role>(1);
			Role l_workerRole = (Role) l_find;
			l_itens.add(l_workerRole);
		}
		return l_itens;
	}
}
